Skip to content
This repository has been archived by the owner on Sep 1, 2020. It is now read-only.

Latest commit

 

History

History
107 lines (95 loc) · 2.85 KB

3.14.3 - 使用类静态变量/全局变量保存上下文.md

File metadata and controls

107 lines (95 loc) · 2.85 KB

使用类静态变量/全局变量保存上下文

多个协程是并发执行的,因此不能使用类静态变量/全局变量保存协程上下文内容。使用局部变量是安全的,因为局部变量的值会自动保存在协程栈中,其他协程访问不到协程的局部变量。

实例

错误的代码

$_array = [];
$serv->on("Request", function ($req, $resp){
	global $_array;
	//请求 /a(协程 1 )
	if ($request->server['request_uri'] == '/a') {
		$_array['name'] = 'a';
		co::sleep(1.0);
		echo $_array['name'];
		$resp->end($_array['name']);
	}
	//请求 /b(协程 2 )
	else {
		$_array['name'] = 'b';
		$resp->end();
	}
});

发起2个并发请求。

curl http://127.0.0.1:9501/a
curl http://127.0.0.1:9501/b
  • 协程1中设置了全局变量$_array['name']的值为a
  • 协程1调用co::sleep挂起
  • 协程2执行,将$_array['name']的值为b,协程2结束
  • 这时定时器返回,底层恢复协程1的运行。而协程1的逻辑中有一个上下文的依赖关系。当再次打印$_array['name']的值时,程序预期是a,但这个值已经被协程2所修改,实际结果却是b,这样就造成了逻辑错误
  • 同理,使用类静态变量Class::$array、全局对象属性$object->array、其他超全局变量$GLOBALS等,进行上下文保存在协程程序中是非常危险的。可能会出现不符合预期的行为。

使用 Context 管理上下文

  • 可以使用一个Context类来管理协程上下文,在Context类中,使用Coroutine::getUid获取了协程ID,然后隔离不同协程之间的全局变量
  • 协程退出时清理上下文数据

Context

use Swoole\Coroutine;

class Context
{
    protected static $pool = [];

    static function get($key)
    {
        $cid = Coroutine::getuid();
        if ($cid < 0)
        {
            return null;
        }
        if(isset(self::$pool[$cid][$key])){
            return self::$pool[$cid][$key];
        }
        return null;
    }

    static function put($key, $item)
    {
        $cid = Coroutine::getuid();
        if ($cid > 0)
        {
            self::$pool[$cid][$key] = $item;
        }

    }

    static function delete($key = null)
    {
        $cid = Coroutine::getuid();
        if ($cid > 0)
        {
            if($key){
                unset(self::$pool[$cid][$key]);
            }else{
                unset(self::$pool[$cid]);
            }
        }
    }
}

正确的代码

$serv->on("Request", function ($req, $resp) {
	if ($request->server['request_uri'] == '/a') {
		Context::put('name', 'a');
		co::sleep(1.0);
		echo Context::get('name');
		$resp->end(Context::get('name'));
		//退出协程时清理
		Context::delete('name');
	} else {
		Context::put('name', 'b');
		$resp->end();
		//退出协程时清理
		Context::delete();
	}
});